﻿// Copyright (c) Microsoft. All rights reserved.

using System;
using System.Collections.Generic;

#pragma warning disable IDE0130 // reduce number of "using" statements
// ReSharper disable once CheckNamespace - reduce number of "using" statements
namespace Microsoft.KernelMemory;

/// <summary>
/// Settings used by the default SearchClient
/// </summary>
public class SearchClientConfig
{
    /// <summary>
    /// Maximum number of tokens accepted by the LLM used to generate answers.
    /// The number includes the tokens used for the answer, e.g. when using
    /// GPT4-32k, set this number to 32768.
    /// If the value is not set or less than one, SearchClient will use the
    /// max amount of tokens supported by the model in use.
    /// </summary>
    public int MaxAskPromptSize { get; set; } = -1;

    /// <summary>
    /// Maximum number of relevant sources to consider when generating an answer.
    /// The value is also used as the max number of results returned by SearchAsync
    /// when passing a limit less or equal to zero.
    /// </summary>
    public int MaxMatchesCount { get; set; } = 100;

    /// <summary>
    /// How many tokens to reserve for the answer generated by the LLM.
    /// E.g. if the LLM supports max 4000 tokens, and AnswerTokens is 300, then
    /// the prompt sent to LLM will contain max 3700 tokens, composed by
    /// prompt + question + grounding information retrieved from memory.
    /// </summary>
    public int AnswerTokens { get; set; } = 300;

    /// <summary>
    /// Text to return when the LLM cannot produce an answer.
    /// </summary>
    public string EmptyAnswer { get; set; } = "INFO NOT FOUND";

    /// <summary>
    /// Number between 0.0 and 2.0. It controls the randomness of the completion.
    /// The higher the temperature, the more random the completion.
    /// </summary>
    public double Temperature { get; set; } = 0;

    /// <summary>
    /// Number between 0.0 and 2.0. It controls the diversity of the completion.
    /// The higher the TopP, the more diverse the completion.
    /// </summary>
    public double TopP { get; set; } = 0;

    /// <summary>
    /// Number between -2.0 and 2.0. Positive values penalize new tokens based on whether
    /// they appear in the text so far, increasing the model's likelihood to talk about
    /// new topics.
    /// </summary>
    public double PresencePenalty { get; set; } = 0;

    /// <summary>
    /// Number between -2.0 and 2.0. Positive values penalize new tokens based on their
    /// existing frequency in the text so far, decreasing the model's likelihood to repeat
    /// the same line verbatim.
    /// </summary>
    public double FrequencyPenalty { get; set; } = 0;

    /// <summary>
    /// Up to 4 sequences where the completion will stop generating further tokens.
    /// </summary>
    public IList<string> StopSequences { get; set; } = Array.Empty<string>();

    /// <summary>
    /// Modify the likelihood of specified tokens appearing in the completion.
    /// </summary>
    public Dictionary<int, float> TokenSelectionBiases { get; set; } = new();

    /// <summary>
    /// Verify that the current state is valid.
    /// </summary>
    public void Validate()
    {
        if (this.MaxAskPromptSize is > 0 and < 1024)
        {
            throw new ArgumentOutOfRangeException(nameof(this.MaxAskPromptSize),
                $"{nameof(this.MaxAskPromptSize)} cannot be less than 1024");
        }

        if (this.MaxMatchesCount < 1)
        {
            throw new ArgumentOutOfRangeException(nameof(this.MaxMatchesCount),
                $"{nameof(this.MaxMatchesCount)} cannot be less than 1");
        }

        if (this.AnswerTokens < 1)
        {
            throw new ArgumentOutOfRangeException(nameof(this.AnswerTokens),
                $"{nameof(this.AnswerTokens)} cannot be less than 1");
        }

        if (this.EmptyAnswer.Length > 256)
        {
            throw new ArgumentOutOfRangeException(nameof(this.EmptyAnswer),
                $"{nameof(this.EmptyAnswer)} is too long, consider something shorter");
        }

        if (this.Temperature is < 0 or > 2)
        {
            throw new ArgumentOutOfRangeException(nameof(this.Temperature),
                $"{nameof(this.Temperature)} must be between 0 and 2");
        }

        if (this.TopP is < 0 or > 2)
        {
            throw new ArgumentOutOfRangeException(nameof(this.TopP),
                $"{nameof(this.TopP)} must be between 0 and 2");
        }

        if (this.PresencePenalty is < -2 or > 2)
        {
            throw new ArgumentOutOfRangeException(nameof(this.PresencePenalty),
                $"{nameof(this.PresencePenalty)} must be between -2 and 2");
        }

        if (this.FrequencyPenalty is < -2 or > 2)
        {
            throw new ArgumentOutOfRangeException(nameof(this.FrequencyPenalty),
                $"{nameof(this.FrequencyPenalty)} must be between -2 and 2");
        }
    }
}
